/**
 ******************************************************************************
 * @file    ring_buffer.c
 * @author  Chavis.Chen
 * @version V1.0.0
 * @date    26-Sep-2018
 * @brief   This file contains the abstraction of ring_buffer.
 ******************************************************************************
 *
 *  The MIT License
 *  Copyright (c) 2018 Chavis.Chen.
 *
 *  Permission is hereby granted, free of charge, to any person obtaining a copy
 *  of this software and associated documentation files (the "Software"), to deal
 *  in the Software without restriction, including without limitation the rights
 *  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 *  copies of the Software, and to permit persons to whom the Software is furnished
 *  to do so, subject to the following conditions:
 *
 *  The above copyright notice and this permission notice shall be included in
 *  all copies or substantial portions of the Software.
 *
 *  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 *  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 *  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 *  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 *  WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
 *  IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 ******************************************************************************
 */


#include "debug.h"
#include "ring_buffer.h"
#include <string.h>
#include <stdlib.h>

static OSStatus ring_buffer_lock(ring_buffer_handle_t * ring_buffer_handle_ptr)
{
	OSStatus err = kNoErr;
	ring_buffer_handle_t ring_buffer_handle = NULL;

	require_action_string(ring_buffer_handle_ptr && *ring_buffer_handle_ptr, exit, err = kParamErr, "parm error");

	ring_buffer_handle = *ring_buffer_handle_ptr;

	err = pthread_mutex_lock(&ring_buffer_handle->mutex);
	require_noerr(err, exit);

exit:
	return err;
}

static OSStatus ring_buffer_unlock(ring_buffer_handle_t * ring_buffer_handle_ptr)
{
	OSStatus err = kNoErr;
	ring_buffer_handle_t ring_buffer_handle = NULL;

	require_action_string(ring_buffer_handle_ptr && *ring_buffer_handle_ptr, exit, err = kParamErr, "parm error");

	ring_buffer_handle = *ring_buffer_handle_ptr;

	err = pthread_mutex_unlock(&ring_buffer_handle->mutex);
	require_noerr(err, exit);

exit:
	return err;
}

static OSStatus ring_buffer_lock_ex(ring_buffer_handle_t * ring_buffer_handle_ptr)
{
	OSStatus err = kNoErr;
	ring_buffer_handle_t ring_buffer_handle = NULL;

	require_action_string(ring_buffer_handle_ptr && *ring_buffer_handle_ptr, exit, err = kParamErr, "parm error");

	ring_buffer_handle = *ring_buffer_handle_ptr;

	err = pthread_mutex_lock(&ring_buffer_handle->mutex_ex);
	require_noerr(err, exit);

exit:
	return err;
}

static OSStatus ring_buffer_unlock_ex(ring_buffer_handle_t * ring_buffer_handle_ptr)
{
	OSStatus err = kNoErr;
	ring_buffer_handle_t ring_buffer_handle = NULL;

	require_action_string(ring_buffer_handle_ptr && *ring_buffer_handle_ptr, exit, err = kParamErr, "parm error");

	ring_buffer_handle = *ring_buffer_handle_ptr;

	err = pthread_mutex_unlock(&ring_buffer_handle->mutex_ex);
	require_noerr(err, exit);

exit:
	return err;
}

OSStatus ring_buffer_init(ring_buffer_handle_t * ring_buffer_handle_ptr, uint32 buffer_size)
{
	OSStatus err = kNoErr;
	ring_buffer_handle_t ring_buffer_handle_tmp = NULL;
	uint8 * buffer_tmp = NULL;
	pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
	pthread_mutex_t mutex_ex = PTHREAD_MUTEX_INITIALIZER;

	require_action_string(ring_buffer_handle_ptr && buffer_size, exit, err = kParamErr, "parm error");

	ring_buffer_handle_tmp = malloc(sizeof(ring_buffer_t));
	require_action_string(ring_buffer_handle_tmp != NULL, exit, err = kNoMemoryErr, "malloc fail");

	buffer_tmp = malloc(buffer_size);
	require_action_string(buffer_tmp != NULL, exit, err = kNoMemoryErr, "malloc fail");

	err = pthread_mutex_init(&mutex, NULL);
	require_noerr(err, exit);

	err = pthread_mutex_init(&mutex_ex, NULL);
	require_noerr(err, exit);

	memset(ring_buffer_handle_tmp, 0, sizeof(ring_buffer_t));
	memset(buffer_tmp, 0, buffer_size);

	* ring_buffer_handle_ptr = ring_buffer_handle_tmp;

	ring_buffer_handle_tmp->buffer = buffer_tmp;
	ring_buffer_handle_tmp->in_pos = 0;
	ring_buffer_handle_tmp->out_pos = 0;
	ring_buffer_handle_tmp->total_size = buffer_size;
	ring_buffer_handle_tmp->used_size = 0;
	ring_buffer_handle_tmp->free_size = buffer_size;
	ring_buffer_handle_tmp->mutex = mutex;
	ring_buffer_handle_tmp->mutex_ex = mutex_ex;

exit:

	if(err != kNoErr)
	{
		if(ring_buffer_handle_ptr != NULL)
			*ring_buffer_handle_ptr = NULL;

		if(ring_buffer_handle_tmp != NULL)
			free(ring_buffer_handle_tmp);

		if(buffer_tmp != NULL)
			free(buffer_tmp);

		pthread_mutex_destroy(&mutex);

		pthread_mutex_destroy(&mutex_ex);
	}
	return err;
}

OSStatus ring_buffer_get_used_size(ring_buffer_handle_t * ring_buffer_handle_ptr, uint32 * size_out)
{
	OSStatus err = kNoErr;

	ring_buffer_handle_t ring_buffer_handle = NULL;

	require_action_string(ring_buffer_handle_ptr && *ring_buffer_handle_ptr && size_out, exit, err = kParamErr, "parm error");

	ring_buffer_handle = *ring_buffer_handle_ptr;

	err = ring_buffer_lock(ring_buffer_handle_ptr);
	require_noerr(err, exit);

	* size_out = ring_buffer_handle->used_size;

	err = ring_buffer_unlock(ring_buffer_handle_ptr);
	require_noerr(err, exit);

exit:
	return err;
}

OSStatus ring_buffer_get_free_size(ring_buffer_handle_t * ring_buffer_handle_ptr, uint32 * size_out)
{
	OSStatus err = kNoErr;
	ring_buffer_handle_t ring_buffer_handle = NULL;

	require_action_string(ring_buffer_handle_ptr && *ring_buffer_handle_ptr && size_out, exit, err = kParamErr, "parm error");

	ring_buffer_handle = *ring_buffer_handle_ptr;

	err = ring_buffer_lock(ring_buffer_handle_ptr);
	require_noerr(err, exit);

	* size_out = ring_buffer_handle->free_size;

	err = ring_buffer_unlock(ring_buffer_handle_ptr);
	require_noerr(err, exit);

exit:
	return err;
}

OSStatus ring_buffer_get_in_pos(ring_buffer_handle_t * ring_buffer_handle_ptr, uint32 * in_pos_out)
{
	OSStatus err = kNoErr;
	ring_buffer_handle_t ring_buffer_handle = NULL;

	require_action_string(ring_buffer_handle_ptr && *ring_buffer_handle_ptr && in_pos_out, exit, err = kParamErr, "parm error");

	ring_buffer_handle = *ring_buffer_handle_ptr;

	err = ring_buffer_lock(ring_buffer_handle_ptr);
	require_noerr(err, exit);

	* in_pos_out = ring_buffer_handle->in_pos;

	err = ring_buffer_unlock(ring_buffer_handle_ptr);
	require_noerr(err, exit);

exit:
	return err;
}

OSStatus ring_buffer_get_out_pos(ring_buffer_handle_t * ring_buffer_handle_ptr, uint32 * out_pos_out)
{
	OSStatus err = kNoErr;
	ring_buffer_handle_t ring_buffer_handle = NULL;

	require_action_string(ring_buffer_handle_ptr && *ring_buffer_handle_ptr && out_pos_out, exit, err = kParamErr, "parm error");

	ring_buffer_handle = *ring_buffer_handle_ptr;

	err = ring_buffer_lock(ring_buffer_handle_ptr);
	require_noerr(err, exit);

	* out_pos_out = ring_buffer_handle->out_pos;

	err = ring_buffer_unlock(ring_buffer_handle_ptr);
	require_noerr(err, exit);

exit:
	return err;
}

OSStatus ring_buffer_get_total_size(ring_buffer_handle_t * ring_buffer_handle_ptr, uint32 * total_size_out)
{
	OSStatus err = kNoErr;
	ring_buffer_handle_t ring_buffer_handle = NULL;

	require_action_string(ring_buffer_handle_ptr && *ring_buffer_handle_ptr && total_size_out, exit, err = kParamErr, "parm error");

	ring_buffer_handle = *ring_buffer_handle_ptr;

	err = ring_buffer_lock(ring_buffer_handle_ptr);
	require_noerr(err, exit);

	* total_size_out = ring_buffer_handle->total_size;

	err = ring_buffer_unlock(ring_buffer_handle_ptr);
	require_noerr(err, exit);

exit:
	return err;
}

OSStatus ring_buffer_write(ring_buffer_handle_t * ring_buffer_handle_ptr, uint8 * data_ptr, uint32 data_size)
{
	OSStatus err = kNoErr;
	uint8 * buffer = NULL;
	uint32 used_size = 0;
	uint32 free_size = 0;
	uint32 in_pos = 0;
	uint32 total_size = 0;
	uint32 to_be_copied_data_size = 0;
	uint32 alrdy_copied_data_size = 0;
	ring_buffer_handle_t ring_buffer_handle = NULL;

	require_action_string(ring_buffer_handle_ptr && *ring_buffer_handle_ptr && data_ptr, exit, err = kParamErr, "parm error");

	ring_buffer_handle = *ring_buffer_handle_ptr;

	buffer		= ring_buffer_handle->buffer;
	used_size	= ring_buffer_handle->used_size;
	free_size 	= ring_buffer_handle->free_size;
	in_pos 		= ring_buffer_handle->in_pos;
	total_size 	= ring_buffer_handle->total_size;

	require_action_string(data_size <= free_size, exit, err = kNotWritableErr, "no enough space to save user data");

	err = ring_buffer_lock(ring_buffer_handle_ptr);
	require_noerr(err, exit);

	/*> ring buffer memory copy mechanism begin */

	to_be_copied_data_size = ((in_pos + data_size)/total_size)*(total_size - in_pos);

	memcpy(buffer + in_pos, data_ptr + alrdy_copied_data_size, to_be_copied_data_size);

	alrdy_copied_data_size = to_be_copied_data_size;				// refresh alrdy_copied_data_size
	in_pos = (in_pos + alrdy_copied_data_size) % total_size;		// refresh in_pos
	to_be_copied_data_size = data_size - alrdy_copied_data_size;	// refresh to_be_copied_data_size

	memcpy(buffer + in_pos, data_ptr + alrdy_copied_data_size, to_be_copied_data_size);

	alrdy_copied_data_size = to_be_copied_data_size;				// refresh alrdy_copied_data_size
	in_pos = (in_pos + alrdy_copied_data_size) % total_size;		// refresh in_pos
	used_size += data_size;											// refresh used_size
	free_size = total_size - used_size;								// refresh free_size

	/*> ring buffer memory copy mechanism end */

	/*> refresh info to ring buffer context */
	ring_buffer_handle->in_pos = in_pos;
	ring_buffer_handle->used_size = used_size;
	ring_buffer_handle->free_size = free_size;

	err = ring_buffer_unlock(ring_buffer_handle_ptr);
	require_noerr(err, exit);

exit:
	return err;
}

OSStatus ring_buffer_read(ring_buffer_handle_t * ring_buffer_handle_ptr, uint8 * data_ptr, uint32 data_size)
{
	OSStatus err = kNoErr;
	uint8 * buffer = NULL;
	uint32 used_size = 0;
	uint32 free_size = 0;
	uint32 out_pos = 0;
	uint32 total_size = 0;
	uint32 to_be_copied_data_size = 0;
	uint32 alrdy_copied_data_size = 0;
	ring_buffer_handle_t ring_buffer_handle = NULL;

	require_action_string(ring_buffer_handle_ptr && *ring_buffer_handle_ptr && data_ptr, exit, err = kParamErr, "parm error");

	ring_buffer_handle = *ring_buffer_handle_ptr;

	buffer		= ring_buffer_handle->buffer;
	used_size	= ring_buffer_handle->used_size;
	free_size 	= ring_buffer_handle->free_size;
	out_pos		= ring_buffer_handle->out_pos;
	total_size 	= ring_buffer_handle->total_size;

	require_action_string(data_size <= used_size, exit, err = kNotReadableErr, "no enough user data to be read");

	err = ring_buffer_lock(ring_buffer_handle_ptr);
	require_noerr(err, exit);

	/*> ring buffer memory copy mechanism begin */

	to_be_copied_data_size = ((out_pos + data_size)/total_size)*(total_size - out_pos);

	memcpy(data_ptr + alrdy_copied_data_size, buffer + out_pos, to_be_copied_data_size);

	alrdy_copied_data_size = to_be_copied_data_size;				// refresh alrdy_copied_data_size
	out_pos = (out_pos + alrdy_copied_data_size) % total_size;		// refresh out_pos
	to_be_copied_data_size = data_size - alrdy_copied_data_size;	// refresh to_be_copied_data_size

	memcpy(data_ptr + alrdy_copied_data_size, buffer + out_pos, to_be_copied_data_size);

	alrdy_copied_data_size = to_be_copied_data_size;				// refresh alrdy_copied_data_size
	out_pos = (out_pos + alrdy_copied_data_size) % total_size;		// refresh out_pos
	free_size += data_size;											// refresh free_size
	used_size = total_size - free_size;								// refresh used_size

	/*> ring buffer memory copy mechanism end */

	/*> refresh info of ring buffer context */
	ring_buffer_handle->out_pos = out_pos;
	ring_buffer_handle->used_size = used_size;
	ring_buffer_handle->free_size = free_size;

	err = ring_buffer_unlock(ring_buffer_handle_ptr);
	require_noerr(err, exit);

exit:
	return err;
}

OSStatus ring_buffer_write_ex(ring_buffer_handle_t * ring_buffer_handle_ptr, uint8 * data_ptr, uint32 data_size, uint32 * actually_write_size_out)
{
	OSStatus err = kNoErr;
	uint32 free_size = 0;
	uint32 actually_write_size = 0;
	bool is_locked = FALSE;

	require_action_string(ring_buffer_handle_ptr && *ring_buffer_handle_ptr && data_ptr, exit, err = kParamErr, "parm error");

	err = ring_buffer_lock_ex(ring_buffer_handle_ptr);
	require_noerr(err, exit);

	is_locked = TRUE;

	err = ring_buffer_get_free_size(ring_buffer_handle_ptr, &free_size);
	require_noerr(err, exit);

	actually_write_size = (data_size <= free_size) ? data_size : free_size;

	err = ring_buffer_write(ring_buffer_handle_ptr, data_ptr, actually_write_size);
	require_noerr(err, exit);

	if(actually_write_size_out)
		* actually_write_size_out = actually_write_size;

	err = ring_buffer_unlock_ex(ring_buffer_handle_ptr);
	require_noerr(err, exit);

exit:
	if(err != kNoErr)
	{
		if(is_locked == TRUE)
			ring_buffer_unlock_ex(ring_buffer_handle_ptr);
	}
	return err;
}

OSStatus ring_buffer_read_ex(ring_buffer_handle_t * ring_buffer_handle_ptr, uint8 * data_ptr, uint32 data_size, uint32 * actually_read_size_out)
{
	OSStatus err = kNoErr;
	uint32 used_size = 0;
	uint32 actually_read_size = 0;
	bool is_locked = FALSE;

	require_action_string(ring_buffer_handle_ptr && *ring_buffer_handle_ptr && data_ptr, exit, err = kParamErr, "parm error");

	err = ring_buffer_lock_ex(ring_buffer_handle_ptr);
	require_noerr(err, exit);

	is_locked = TRUE;

	err = ring_buffer_get_used_size(ring_buffer_handle_ptr, &used_size);
	require_noerr(err, exit);

	actually_read_size = (data_size <= used_size) ? data_size : used_size;

	err = ring_buffer_read(ring_buffer_handle_ptr, data_ptr, actually_read_size);
	require_noerr(err, exit);

	if(actually_read_size_out)
		* actually_read_size_out = actually_read_size;

	err = ring_buffer_unlock_ex(ring_buffer_handle_ptr);
	require_noerr(err, exit);

exit:
	if(err != kNoErr)
	{
		if(is_locked == TRUE)
			ring_buffer_unlock_ex(ring_buffer_handle_ptr);
	}
	return err;
}

OSStatus ring_buffer_reset(ring_buffer_handle_t * ring_buffer_handle_ptr)
{
	OSStatus err = kNoErr;
	bool is_locked = FALSE;
	ring_buffer_handle_t ring_buffer_handle = NULL;

	require_action_string(ring_buffer_handle_ptr && *ring_buffer_handle_ptr, exit, err = kParamErr, "parm error");

	ring_buffer_handle = *ring_buffer_handle_ptr;

	err = ring_buffer_lock(ring_buffer_handle_ptr);
	require_noerr(err, exit);

	is_locked = TRUE;

	memset(ring_buffer_handle->buffer, 0, ring_buffer_handle->total_size);

	ring_buffer_handle->in_pos = 0;
	ring_buffer_handle->out_pos = 0;
	ring_buffer_handle->used_size = 0;
	ring_buffer_handle->free_size = ring_buffer_handle->total_size;

	err = ring_buffer_unlock(ring_buffer_handle_ptr);
	require_noerr(err, exit);

exit:
	if(err != kNoErr)
	{
		if(is_locked == TRUE)
			ring_buffer_unlock(ring_buffer_handle_ptr);
	}
	return err;
}

OSStatus ring_buffer_deinit(ring_buffer_handle_t * ring_buffer_handle_ptr)
{
	OSStatus err = kNoErr;
	ring_buffer_handle_t ring_buffer_handle = NULL;

	require_action_string(ring_buffer_handle_ptr && *ring_buffer_handle_ptr, exit, err = kParamErr, "parm error");

	ring_buffer_handle = *ring_buffer_handle_ptr;

	if(ring_buffer_handle->buffer != NULL)
		free(ring_buffer_handle->buffer);

	pthread_mutex_destroy(&ring_buffer_handle->mutex);

	pthread_mutex_destroy(&ring_buffer_handle->mutex_ex);

	memset(ring_buffer_handle, 0, sizeof(ring_buffer_t));
	free(ring_buffer_handle);
	*ring_buffer_handle_ptr = NULL;

exit:
	return err;
}

